﻿///////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2023, ООО 1С-Софт
// Все права защищены. Эта программа и сопроводительные материалы предоставляются 
// в соответствии с условиями лицензии Attribution 4.0 International (CC BY 4.0)
// Текст лицензии доступен по ссылке:
// https://creativecommons.org/licenses/by/4.0/legalcode
///////////////////////////////////////////////////////////////////////////////////////////////////////

#Область ПрограммныйИнтерфейс

// Начинает замер времени ключевой операции. Закончить замер нужно явно вызовом
// процедуры ЗакончитьЗамерВремени или ЗакончитьЗамерВремениТехнологический.
//
// Возвращаемое значение:
//  Число - число длиной 14 символов, время UTC начала с точностью до миллисекунд.
//
Функция НачатьЗамерВремени() Экспорт

	ВремяНачала = 0;

	Если ОценкаПроизводительностиВызовСервераПовтИсп.ВыполнятьЗамерыПроизводительности() Тогда
		ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
	КонецЕсли;

	Возврат ВремяНачала;

КонецФункции

// Завершает замер времени ключевой операции
// и записывает результат в регистр сведений ЗамерыВремени.
//
// Параметры:
//   КлючеваяОперация	- СправочникСсылка.КлючевыеОперации
//                   	- Строка - ключевая операция.
//  ВремяНачала			- Число										- универсальная дата в миллисекундах,
//								  				  					  возвращаемая при начале замера функцией ОценкаПроизводительности.НачатьЗамерВремени.
//  ВесЗамера			- Число										- количественный показатель замера, например количество строк в документе.
//  Комментарий			- Строка
//             			- Соответствие - произвольная информация замера.
//  ВыполненСОшибкой	- Булево									- признак того, что замер не был выполнен до конца,
//
Процедура ЗакончитьЗамерВремени(КлючеваяОперация, ВремяНачала, ВесЗамера = 1, Комментарий = Неопределено,
	ВыполненСОшибкой = Ложь) Экспорт
	Если ОценкаПроизводительностиВызовСервераПовтИсп.ВыполнятьЗамерыПроизводительности() Тогда
		ВремяОкончания = ТекущаяУниверсальнаяДатаВМиллисекундах();
		Длительность = (ВремяОкончания - ВремяНачала) / 1000;

		ПараметрыЗамера = Новый Структура;
		ПараметрыЗамера.Вставить("КлючеваяОперация", КлючеваяОперация);
		ПараметрыЗамера.Вставить("Длительность", Длительность);
		ПараметрыЗамера.Вставить("ДатаНачалаКлючевойОперации", ВремяНачала);
		ПараметрыЗамера.Вставить("ДатаОкончанияКлючевойОперации", ВремяОкончания);
		ПараметрыЗамера.Вставить("ВесЗамера", ВесЗамера);
		ПараметрыЗамера.Вставить("Комментарий", Комментарий);
		ПараметрыЗамера.Вставить("Технологический", Ложь);
		ПараметрыЗамера.Вставить("Длительная", Ложь);
		ПараметрыЗамера.Вставить("ВыполненСОшибкой", ВыполненСОшибкой);

		ЗафиксироватьДлительностьКлючевойОперации(ПараметрыЗамера);
	КонецЕсли;
КонецПроцедуры

// Завершает замер времени ключевой операции
// и записывает результат в регистр сведений ЗамерыВремениТехнологические.
//
// Параметры:
//   КлючеваяОперация	- СправочникСсылка.КлючевыеОперации
//                   	- Строка - ключевая операция.
//  ВремяНачала			- Число										- универсальная дата в миллисекундах,
//								  				  					  возвращаемая при начале замера функцией ОценкаПроизводительности.НачатьЗамерВремени.
//  ВесЗамера			- Число										- количественный показатель замера, например количество строк в документе.
//  Комментарий			- Строка
//             			- Соответствие - произвольная информация замера.
//
Процедура ЗакончитьЗамерВремениТехнологический(КлючеваяОперация, ВремяНачала, ВесЗамера = 1,
	Комментарий = Неопределено) Экспорт
	Если ОценкаПроизводительностиВызовСервераПовтИсп.ВыполнятьЗамерыПроизводительности() Тогда
		ВремяОкончания = ТекущаяУниверсальнаяДатаВМиллисекундах();
		Длительность = (ВремяОкончания - ВремяНачала) / 1000;

		ПараметрыЗамера = Новый Структура;
		ПараметрыЗамера.Вставить("КлючеваяОперация", КлючеваяОперация);
		ПараметрыЗамера.Вставить("Длительность", Длительность);
		ПараметрыЗамера.Вставить("ДатаНачалаКлючевойОперации", ВремяНачала);
		ПараметрыЗамера.Вставить("ДатаОкончанияКлючевойОперации", ВремяОкончания);
		ПараметрыЗамера.Вставить("ВесЗамера", ВесЗамера);
		ПараметрыЗамера.Вставить("Комментарий", Комментарий);
		ПараметрыЗамера.Вставить("Технологический", Истина);
		ПараметрыЗамера.Вставить("Длительная", Ложь);
		ПараметрыЗамера.Вставить("ВыполненСОшибкой", Ложь);

		ЗафиксироватьДлительностьКлючевойОперации(ПараметрыЗамера);
	КонецЕсли;
КонецПроцедуры

// Создает ключевые операции в случае их отсутствия.
//
// Параметры:
//  КлючевыеОперации - Массив - ключевые операции, элемент массива - Структура("ИмяКлючевойОперации, ЦелевоеВремя").
//
Процедура СоздатьКлючевыеОперации(КлючевыеОперации) Экспорт
	Таблица = Новый ТаблицаЗначений;
	Таблица.Колонки.Добавить("ИмяКлючевойОперации", Новый ОписаниеТипов("Строка", , , ,
		Новый КвалификаторыСтроки(1000)));
	Таблица.Колонки.Добавить("ЦелевоеВремя", Новый ОписаниеТипов("Число", , , Новый КвалификаторыЧисла(15, 2)));

	Для Каждого КлючеваяОперация Из КлючевыеОперации Цикл
		ЗаполнитьЗначенияСвойств(Таблица.Добавить(), КлючеваяОперация);
	КонецЦикла;

	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ
				   |	Таблица.ИмяКлючевойОперации КАК ИмяКлючевойОперации,
				   |	Таблица.ЦелевоеВремя КАК ЦелевоеВремя
				   |ПОМЕСТИТЬ Таблица
				   |ИЗ
				   |	&Таблица КАК Таблица
				   |;
				   |
				   |////////////////////////////////////////////////////////////////////////////////
				   |ВЫБРАТЬ
				   |	ЕСТЬNULL(КлючевыеОперации.Ссылка, ЗНАЧЕНИЕ(Справочник.КлючевыеОперации.ПустаяСсылка)) КАК Ссылка,
				   |	Таблица.ИмяКлючевойОперации КАК ИмяКлючевойОперации,
				   |	Таблица.ЦелевоеВремя КАК ЦелевоеВремя
				   |ИЗ
				   |	Таблица КАК Таблица
				   |		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.КлючевыеОперации КАК КлючевыеОперации
				   |		ПО Таблица.ИмяКлючевойОперации = КлючевыеОперации.Имя";
	Запрос.УстановитьПараметр("Таблица", Таблица);
	РезультатЗапроса = Запрос.Выполнить();
	Если РезультатЗапроса.Пустой() Тогда
		Возврат;
	КонецЕсли;
	Выборка = РезультатЗапроса.Выбрать();

	Пока Выборка.Следующий() Цикл
		Если Выборка.Ссылка.Пустая() Тогда
			СоздатьКлючевуюОперацию(Выборка.ИмяКлючевойОперации, Выборка.ЦелевоеВремя); // @skip-check query-in-loop - создание ключевой операции в случае отсутствия.
		КонецЕсли;
	КонецЦикла;
КонецПроцедуры

// Устанавливает ключевой операции новое целевое время.
//
// Параметры:
//  КлючевыеОперации - Массив - ключевые операции, элемент массива - Структура("ИмяКлючевойОперации, ЦелевоеВремя").
//
Процедура УстановитьЦелевоеВремя(КлючевыеОперации) Экспорт

	Таблица = Новый ТаблицаЗначений;
	Таблица.Колонки.Добавить("ИмяКлючевойОперации", Новый ОписаниеТипов("Строка", , , ,
		Новый КвалификаторыСтроки(1000)));
	Таблица.Колонки.Добавить("ЦелевоеВремя", Новый ОписаниеТипов("Число", , , Новый КвалификаторыЧисла(15, 2)));

	Для Каждого КлючеваяОперация Из КлючевыеОперации Цикл
		ЗаполнитьЗначенияСвойств(Таблица.Добавить(), КлючеваяОперация);
	КонецЦикла;

	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ
				   |	Таблица.ИмяКлючевойОперации КАК ИмяКлючевойОперации,
				   |	Таблица.ЦелевоеВремя КАК ЦелевоеВремя
				   |ПОМЕСТИТЬ Таблица
				   |ИЗ
				   |	&Таблица КАК Таблица
				   |;
				   |
				   |////////////////////////////////////////////////////////////////////////////////
				   |ВЫБРАТЬ
				   |	КлючевыеОперации.Ссылка КАК Ссылка,
				   |	Таблица.ЦелевоеВремя КАК ЦелевоеВремя
				   |ИЗ
				   |	Справочник.КлючевыеОперации КАК КлючевыеОперации
				   |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Таблица КАК Таблица
				   |		ПО КлючевыеОперации.Имя = Таблица.ИмяКлючевойОперации
				   |
				   |УПОРЯДОЧИТЬ ПО
				   |	Ссылка";
	Запрос.УстановитьПараметр("Таблица", Таблица);
	РезультатЗапроса = Запрос.Выполнить();
	Если РезультатЗапроса.Пустой() Тогда
		Возврат;
	КонецЕсли;
	Выборка = РезультатЗапроса.Выбрать();

	НачатьТранзакцию();
	Попытка
		Блокировка = Новый БлокировкаДанных;
		Для Каждого КлючеваяОперация Из КлючевыеОперации Цикл
			ЭлементБлокировки = Блокировка.Добавить("Справочник.КлючевыеОперации");
			ЭлементБлокировки.УстановитьЗначение("Имя", КлючеваяОперация.ИмяКлючевойОперации);
		КонецЦикла;
		Блокировка.Заблокировать();
		Пока Выборка.Следующий() Цикл
			КлючеваяОперацияОбъект = Выборка.Ссылка.ПолучитьОбъект(); // СправочникОбъект.КлючевыеОперации
			КлючеваяОперацияОбъект.ЦелевоеВремя = Выборка.ЦелевоеВремя;
			КлючеваяОперацияОбъект.Записать();
		КонецЦикла;
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;

КонецПроцедуры

// Изменяет ключевые операции.
//
// Параметры:
//  КлючевыеОперации - Массив - ключевые операции,
//								элемент массива - Структура("ИмяКлючевойОперацииСтарое, ИмяКлючевойОперацииНовое , ЦелевоеВремя")
//								или
//								элемент массива - Структура("ИмяКлючевойОперацииСтарое, ИмяКлючевойОперацииНовое"),
//								целевое время не изменяется.
//
Процедура ИзменитьКлючевыеОперации(КлючевыеОперации) Экспорт

	Таблица = Новый ТаблицаЗначений;
	Таблица.Колонки.Добавить("ИмяКлючевойОперацииСтарое", Новый ОписаниеТипов("Строка", , , ,
		Новый КвалификаторыСтроки(1000)));
	Таблица.Колонки.Добавить("ИмяКлючевойОперацииНовое", Новый ОписаниеТипов("Строка", , , ,
		Новый КвалификаторыСтроки(1000)));
	Таблица.Колонки.Добавить("ЦелевоеВремя", Новый ОписаниеТипов("Число", , , Новый КвалификаторыЧисла(15, 2)));

	Для Каждого КлючеваяОперация Из КлючевыеОперации Цикл
		ЗаполнитьЗначенияСвойств(Таблица.Добавить(), КлючеваяОперация);
	КонецЦикла;

	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ
				   |	Таблица.ИмяКлючевойОперацииСтарое КАК ИмяКлючевойОперацииСтарое,
				   |	Таблица.ИмяКлючевойОперацииНовое КАК ИмяКлючевойОперацииНовое,
				   |	Таблица.ЦелевоеВремя КАК ЦелевоеВремя
				   |ПОМЕСТИТЬ Таблица
				   |ИЗ
				   |	&Таблица КАК Таблица
				   |;
				   |
				   |////////////////////////////////////////////////////////////////////////////////
				   |ВЫБРАТЬ
				   |	КлючевыеОперации.Ссылка КАК Ссылка,
				   |	Таблица.ИмяКлючевойОперацииНовое КАК ИмяКлючевойОперацииНовое,
				   |	ВЫБОР
				   |		КОГДА Таблица.ЦелевоеВремя = 0
				   |			ТОГДА КлючевыеОперации.ЦелевоеВремя
				   |		ИНАЧЕ Таблица.ЦелевоеВремя
				   |	КОНЕЦ КАК ЦелевоеВремя
				   |ИЗ
				   |	Справочник.КлючевыеОперации КАК КлючевыеОперации
				   |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Таблица КАК Таблица
				   |		ПО КлючевыеОперации.Имя = Таблица.ИмяКлючевойОперацииСтарое
				   |
				   |УПОРЯДОЧИТЬ ПО
				   |	Ссылка";
	Запрос.УстановитьПараметр("Таблица", Таблица);
	РезультатЗапроса = Запрос.Выполнить();
	Если РезультатЗапроса.Пустой() Тогда
		Возврат;
	КонецЕсли;
	Выборка = РезультатЗапроса.Выбрать();

	НачатьТранзакцию();
	Попытка

		Блокировка = Новый БлокировкаДанных;
		Для Каждого КлючеваяОперация Из КлючевыеОперации Цикл
			ЭлементБлокировки = Блокировка.Добавить("Справочник.КлючевыеОперации");
			ЭлементБлокировки.УстановитьЗначение("Имя", КлючеваяОперация.ИмяКлючевойОперацииСтарое);

			ЭлементБлокировки = Блокировка.Добавить("Справочник.КлючевыеОперации");
			ЭлементБлокировки.УстановитьЗначение("Имя", КлючеваяОперация.ИмяКлючевойОперацииНовое);
		КонецЦикла;
		Блокировка.Заблокировать();

		Пока Выборка.Следующий() Цикл
			КлючеваяОперацияОбъект = Выборка.Ссылка.ПолучитьОбъект(); // СправочникОбъект.КлючевыеОперации
			КлючеваяОперацияОбъект.Имя = Выборка.ИмяКлючевойОперацииНовое;
			КлючеваяОперацияОбъект.Наименование = РазложитьСтрокуПоСловам(Выборка.ИмяКлючевойОперацииНовое);
			КлючеваяОперацияОбъект.ЦелевоеВремя = Выборка.ЦелевоеВремя;
			КлючеваяОперацияОбъект.Записать();
		КонецЦикла;
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;

КонецПроцедуры

// Начинает замер времени длительной ключевой операции. Закончить замер нужно явно вызовом
// процедуры ЗакончитьЗамерДлительнойОперации.
//
// Параметры:
//   КлючеваяОперация	- Строка - ключевая операция.
//
// Возвращаемое значение:
//   Соответствие из КлючИЗначение:
//     * Ключ - Строка
//     * Значение - Произвольный
//   Ключи:
//    # КлючеваяОперация - Строка - имя ключевой операции.
//    # ВремяНачала - Число - время начала ключевой операции в миллисекундах.
//    # ВремяПоследнегоЗамера - Число - время последнего замера ключевой операции в миллисекундах.
//    # ВесЗамера - Число - количество данных, обработанных в ходе выполнения действий.
//    # ВложенныеЗамеры - Соответствие - коллекция замеров вложенных шагов.
//
Функция НачатьЗамерДлительнойОперации(КлючеваяОперация) Экспорт

	ОписаниеЗамера = Новый Соответствие;
	Если ОценкаПроизводительностиВызовСервераПовтИсп.ВыполнятьЗамерыПроизводительности() Тогда
		ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
		ОписаниеЗамера.Вставить("КлючеваяОперация", КлючеваяОперация);
		ОписаниеЗамера.Вставить("ВремяНачала", ВремяНачала);
		ОписаниеЗамера.Вставить("ВремяПоследнегоЗамера", ВремяНачала);
		ОписаниеЗамера.Вставить("ВесЗамера", 0);
		ОписаниеЗамера.Вставить("ВложенныеЗамеры", Новый Соответствие);
	КонецЕсли;

	Возврат ОписаниеЗамера;

КонецФункции

// Фиксирует замер вложенного шага длительной операции.
// Параметры:
//  ОписаниеЗамера 		- Соответствие	 - должно быть получено вызовом метода НачатьЗамерДлительнойОперации.
//  КоличествоДанных 	- Число			 - количество данных, например, строк, обработанных в ходе выполнения вложенного шага.
//  ИмяШага 			- Строка		 - произвольное имя вложенного шага.
//  Комментарий 		- Строка		 - произвольное дополнительное описание замера.
//
Процедура ЗафиксироватьЗамерДлительнойОперации(ОписаниеЗамера, КоличествоДанных, ИмяШага, Комментарий = "") Экспорт

	Если Не ЗначениеЗаполнено(ОписаниеЗамера) Тогда
		Возврат;
	КонецЕсли;

	ТекущееВремя = ТекущаяУниверсальнаяДатаВМиллисекундах();

	Длительность = ТекущееВремя - ОписаниеЗамера["ВремяПоследнегоЗамера"];
	КоличествоДанныхШага = ?(КоличествоДанных = 0, 1, КоличествоДанных);
	// Если вложенный замер выполняется первый раз, то инициализируем его.
	ВложенныеЗамеры = ОписаниеЗамера["ВложенныеЗамеры"];
	Если ВложенныеЗамеры[ИмяШага] = Неопределено Тогда
		ВложенныеЗамеры.Вставить(ИмяШага, Новый Соответствие);
		ШагВложенногоЗамера = ВложенныеЗамеры[ИмяШага];
		ШагВложенногоЗамера.Вставить("Комментарий", Комментарий);
		ШагВложенногоЗамера.Вставить("ВремяНачала", ОписаниеЗамера["ВремяПоследнегоЗамера"]);
		ШагВложенногоЗамера.Вставить("Длительность", 0.0);
		ШагВложенногоЗамера.Вставить("ВесЗамера", 0);
	КонецЕсли;                                                            
	// Пишем данные вложенного замера.
	ШагВложенногоЗамера = ВложенныеЗамеры[ИмяШага];
	ШагВложенногоЗамера.Вставить("ВремяОкончания", ТекущееВремя);
	ШагВложенногоЗамера.Вставить("Длительность", Длительность + ШагВложенногоЗамера["Длительность"]);
	ШагВложенногоЗамера.Вставить("ВесЗамера", КоличествоДанныхШага + ШагВложенногоЗамера["ВесЗамера"]);
	
	// Пишем данные длительного замера
	ОписаниеЗамера.Вставить("ВремяПоследнегоЗамера", ТекущееВремя);
	ОписаниеЗамера.Вставить("ВесЗамера", КоличествоДанныхШага + ОписаниеЗамера["ВесЗамера"]);

КонецПроцедуры

// Завершает замер длительной операции.
// Если указано имя шага, фиксирует его как отдельный вложенный шаг
// Параметры:
//  ОписаниеЗамера 		- Соответствие	 - должно быть получено вызовом метода НачатьЗамерДлительнойОперации.
//  КоличествоДанных 	- Число			 - количество данных, например, строк, обработанных в ходе выполнения вложенного шага.
//  ИмяШага 			- Строка		 - произвольное имя вложенного шага.
//  Комментарий 		- Строка		 - произвольное дополнительное описание замера.
//
Процедура ЗакончитьЗамерДлительнойОперации(ОписаниеЗамера, КоличествоДанных, ИмяШага = "", Комментарий = "") Экспорт

	Если Не ЗначениеЗаполнено(ОписаниеЗамера) Тогда
		Возврат;
	КонецЕсли;

	Если ОписаниеЗамера["Клиентский"] = Истина Тогда
		Возврат;
	КонецЕсли;
	
	// Переменные из описания замера.
	ВремяНачалаЗамера	 = ОписаниеЗамера["ВремяНачала"];
	ИмяКлючевойОперации	 = ОписаниеЗамера["КлючеваяОперация"];
	ВложенныеЗамеры		 = ОписаниеЗамера["ВложенныеЗамеры"];
	
	// Расчетные переменные
	ЕстьВложенныеЗамеры = ВложенныеЗамеры.Количество();
	ТекущееВремя = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Длительность = ТекущееВремя - ВремяНачалаЗамера;
	УдельноеВремяОбщее = 0;
	
	// Если не указано имя шага, но есть вложенные шаги, то имя по умолчанию "ПоследнийШаг".
	КоличествоДанныхШага = ?(КоличествоДанных = 0, 1, КоличествоДанных);
	Если ЕстьВложенныеЗамеры Тогда
		ЗафиксироватьЗамерДлительнойОперации(ОписаниеЗамера, КоличествоДанныхШага, ?(ПустаяСтрока(ИмяШага),
			"ПоследнийШаг", ИмяШага), Комментарий);
	КонецЕсли;

	МассивЗамеровДляЗаписи = Новый Массив;

	Для Каждого Замер Из ВложенныеЗамеры Цикл
		ДанныеЗамера = Замер.Значение;
		ВесВложенногоЗамера = ДанныеЗамера["ВесЗамера"];
		ДлительностьВложенногоЗамера = ДанныеЗамера["Длительность"];
		КлючеваяОперация = ИмяКлючевойОперации + "." + Замер.Ключ;
		УдельноеВремя = ?(ВесВложенногоЗамера = 0, ДлительностьВложенногоЗамера, ДлительностьВложенногоЗамера
			/ ВесВложенногоЗамера);
		УдельноеВремяОбщее = УдельноеВремяОбщее + УдельноеВремя;

		ПараметрыЗамера = Новый Структура;
		ПараметрыЗамера.Вставить("КлючеваяОперация", КлючеваяОперация);
		ПараметрыЗамера.Вставить("Длительность", УдельноеВремя / 1000);
		ПараметрыЗамера.Вставить("ДатаНачалаКлючевойОперации", ДанныеЗамера["ВремяНачала"]);
		ПараметрыЗамера.Вставить("ДатаОкончанияКлючевойОперации", ДанныеЗамера["ВремяОкончания"]);
		ПараметрыЗамера.Вставить("ВесЗамера", ВесВложенногоЗамера);
		ПараметрыЗамера.Вставить("Комментарий", ДанныеЗамера["Комментарий"]);
		ПараметрыЗамера.Вставить("Технологический", Ложь);
		ПараметрыЗамера.Вставить("Длительная", Истина);

		МассивЗамеровДляЗаписи.Добавить(ПараметрыЗамера);
	КонецЦикла;
	
	// Фиксация удельного времени ключевой операции.
	ПараметрыЗамера = Новый Структура;
	ПараметрыЗамера.Вставить("КлючеваяОперация", ИмяКлючевойОперации + ".Удельный");
	ПараметрыЗамера.Вставить("ДатаНачалаКлючевойОперации", ВремяНачалаЗамера);
	ПараметрыЗамера.Вставить("ДатаОкончанияКлючевойОперации", ТекущееВремя);
	ПараметрыЗамера.Вставить("Комментарий", Комментарий);
	ПараметрыЗамера.Вставить("Технологический", Ложь);
	ПараметрыЗамера.Вставить("Длительная", Истина);

	Если ЕстьВложенныеЗамеры Тогда
		ПараметрыЗамера.Вставить("Длительность", УдельноеВремяОбщее / 1000);
		ПараметрыЗамера.Вставить("ВесЗамера", ОписаниеЗамера["ВесЗамера"]);
	Иначе
		// Если вложенных замеров не было, то фиксируется удельный замер.
		ПараметрыЗамера.Вставить("Длительность", Длительность / 1000 / КоличествоДанныхШага);
		ПараметрыЗамера.Вставить("ВесЗамера", КоличествоДанныхШага);
	КонецЕсли;
	МассивЗамеровДляЗаписи.Добавить(ПараметрыЗамера);
	
	// Фиксация длительной ключевой операции.
	ПараметрыЗамера = Новый Структура;
	ПараметрыЗамера.Вставить("КлючеваяОперация", ИмяКлючевойОперации);
	ПараметрыЗамера.Вставить("Длительность", (Длительность) / 1000);
	ПараметрыЗамера.Вставить("ДатаНачалаКлючевойОперации", ВремяНачалаЗамера);
	ПараметрыЗамера.Вставить("ДатаОкончанияКлючевойОперации", ТекущееВремя);
	Если ЕстьВложенныеЗамеры Тогда
		ПараметрыЗамера.Вставить("ВесЗамера", ОписаниеЗамера["ВесЗамера"]);
	Иначе
		ПараметрыЗамера.Вставить("ВесЗамера", КоличествоДанныхШага);
	КонецЕсли;
	ПараметрыЗамера.Вставить("Комментарий", Комментарий);
	ПараметрыЗамера.Вставить("Технологический", Ложь);
	ПараметрыЗамера.Вставить("Длительная", Ложь);

	МассивЗамеровДляЗаписи.Добавить(ПараметрыЗамера);

	ЗаписатьЗамерыВремени(МассивЗамеровДляЗаписи);

КонецПроцедуры

#Область УстаревшиеПроцедурыИФункции

// Устарела. Будет удалена в следующей редакции библиотеки.
// Устанавливает ключевой операции признака ошибки.
//
// Параметры:
//  КлючевыеОперации - Массив - ключевые операции, элемент массива - Структура("ИмяКлючевойОперации, Признак").
//
Процедура УстановитьПризнакЗавершенияСОшибкой(КлючевыеОперации) Экспорт

	Запрос = Новый Запрос;
	Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1
				   |	КлючевыеОперации.Ссылка КАК Ссылка
				   |ИЗ
				   |	Справочник.КлючевыеОперации КАК КлючевыеОперации
				   |ГДЕ
				   |	КлючевыеОперации.Имя = &Имя
				   |УПОРЯДОЧИТЬ ПО
				   |	Ссылка";

	НачатьТранзакцию();
	Попытка

		Блокировка = Новый БлокировкаДанных;
		Для Каждого КлючеваяОперация Из КлючевыеОперации Цикл
			ЭлементБлокировки = Блокировка.Добавить("Справочник.КлючевыеОперации");
			ЭлементБлокировки.УстановитьЗначение("Имя", КлючеваяОперация.ИмяКлючевойОперации);
		КонецЦикла;
		Блокировка.Заблокировать();

		Для Каждого КлючеваяОперация Из КлючевыеОперации Цикл
			Запрос.УстановитьПараметр("Имя", КлючеваяОперация.ИмяКлючевойОперации);
			РезультатЗапроса = Запрос.Выполнить(); // @skip-check query-in-loop - Устаревший код не подлежит исправлению.
			Если Не РезультатЗапроса.Пустой() Тогда
				Выборка = РезультатЗапроса.Выбрать();
				Выборка.Следующий();
				КлючеваяОперацияСсылка = Выборка.Ссылка;
				КлючеваяОперацияОбъект = КлючеваяОперацияСсылка.ПолучитьОбъект(); // СправочникОбъект.КлючевыеОперации
				КлючеваяОперацияОбъект.ЗавершенаСОшибкой = КлючеваяОперация.Признак;

				КлючеваяОперацияОбъект.Записать();
			КонецЕсли;
		КонецЦикла;
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;

КонецПроцедуры

#КонецОбласти

#КонецОбласти

#Область СлужебныйПрограммныйИнтерфейс

// Включает/выключает замеры производительности
//
Процедура ВключитьЗамерыПроизводительности(Параметр) Экспорт

	Константы.ВыполнятьЗамерыПроизводительности.Установить(Параметр);

КонецПроцедуры

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

// Создает новый элемент справочника "Ключевые операции".
//
// Параметры:
//  ИмяКлючевойОперации - Строка - название ключевой операции.
//  ЦелевоеВремя - Число - целевое время ключевой операции.
//  Длительная - Булево - признак фиксации удельного времени для замера ключевой операции.
//
// Возвращаемое значение:
//  СправочникСсылка.КлючевыеОперации.
//
Функция СоздатьКлючевуюОперацию(ИмяКлючевойОперации, ЦелевоеВремя = 1, Длительная = Ложь) Экспорт

	УстановитьПривилегированныйРежим(Истина);

	НачатьТранзакцию();

	Попытка
		Блокировка = Новый БлокировкаДанных;
		ЭлементБлокировки = Блокировка.Добавить("Справочник.КлючевыеОперации");
		ЭлементБлокировки.УстановитьЗначение("Имя", ИмяКлючевойОперации);
		Блокировка.Заблокировать();

		Запрос = Новый Запрос;
		Запрос.Текст = "ВЫБРАТЬ ПЕРВЫЕ 1
					   |	КлючевыеОперации.Ссылка КАК Ссылка
					   |ИЗ
					   |	Справочник.КлючевыеОперации КАК КлючевыеОперации
					   |ГДЕ
					   |	КлючевыеОперации.ИмяХеш = &ИмяХеш
					   |
					   |УПОРЯДОЧИТЬ ПО
					   |	Ссылка";

		ХешMD5 = Новый ХешированиеДанных(ХешФункция.MD5);
		ХешMD5.Добавить(ИмяКлючевойОперации);
		ИмяХеш = ХешMD5.ХешСумма;
		ИмяХеш = СтрЗаменить(Строка(ИмяХеш), " ", "");

		Запрос.УстановитьПараметр("ИмяХеш", ИмяХеш);
		РезультатЗапроса = Запрос.Выполнить();
		Если РезультатЗапроса.Пустой() Тогда
			Наименование = РазложитьСтрокуПоСловам(ИмяКлючевойОперации);

			НовыйЭлемент = ОценкаПроизводительностиСлужебный.СлужебныйЭлемент(
				Справочники.КлючевыеОперации);
			НовыйЭлемент.Имя = ИмяКлючевойОперации;
			НовыйЭлемент.Наименование = Наименование;
			НовыйЭлемент.ИмяХеш = ИмяХеш;
			НовыйЭлемент.ЦелевоеВремя = ЦелевоеВремя;
			НовыйЭлемент.Длительная = Длительная;
			НовыйЭлемент.Записать();
			КлючеваяОперацияСсылка = НовыйЭлемент.Ссылка;
		Иначе
			Выборка = РезультатЗапроса.Выбрать();
			Выборка.Следующий();
			КлючеваяОперацияСсылка = Выборка.Ссылка;
		КонецЕсли;

		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;

	Возврат КлючеваяОперацияСсылка;

КонецФункции

// Разбивает строку из нескольких объединенных слов в строку с отдельными словами.
// Признаком начала нового слова считается символ в верхнем регистре.
//
// Параметры:
//  Строка                 - Строка - текст с разделителями;
//
// Возвращаемое значение:
//  Строка - строка, разделенная по словам.
//
// Примеры:
//  РазложитьСтрокуПоСловам("ОдинДваТри") - возвратит строку "Один два три".
//  РазложитьСтрокуПоСловам("одиндватри") - возвратит строку "одиндватри".
//
Функция РазложитьСтрокуПоСловам(Знач Строка)

	МассивСлов = Новый Массив;

	ПозицииСлов = Новый Массив;
	Для ПозицияСимвола = 1 По СтрДлина(Строка) Цикл
		ТекСимвол = Сред(Строка, ПозицияСимвола, 1);
		Если ТекСимвол = ВРег(ТекСимвол) И (ОценкаПроизводительностиКлиентСервер.ТолькоКириллицаВСтроке(ТекСимвол)
			Или ОценкаПроизводительностиКлиентСервер.ТолькоЛатиницаВСтроке(ТекСимвол)) Тогда
			ПозицииСлов.Добавить(ПозицияСимвола);
		КонецЕсли;
	КонецЦикла;

	Если ПозицииСлов.Количество() > 0 Тогда
		ПредыдущаяПозиция = 0;
		Для Каждого Позиция Из ПозицииСлов Цикл
			Если ПредыдущаяПозиция > 0 Тогда
				Подстрока = Сред(Строка, ПредыдущаяПозиция, Позиция - ПредыдущаяПозиция);
				Если Не ПустаяСтрока(Подстрока) Тогда
					МассивСлов.Добавить(СокрЛП(Подстрока));
				КонецЕсли;
			КонецЕсли;
			ПредыдущаяПозиция = Позиция;
		КонецЦикла;

		Подстрока = Сред(Строка, Позиция);
		Если Не ПустаяСтрока(Подстрока) Тогда
			МассивСлов.Добавить(СокрЛП(Подстрока));
		КонецЕсли;
	КонецЕсли;

	Для Индекс = 1 По МассивСлов.ВГраница() Цикл
		МассивСлов[Индекс] = НРег(МассивСлов[Индекс]);
	КонецЦикла;

	Если МассивСлов.Количество() <> 0 Тогда
		Результат = СтрСоединить(МассивСлов, " ");
	Иначе
		Результат = Строка;
	КонецЕсли;

	Возврат Результат;

КонецФункции

// Текущее значение периода записи результатов замеров на сервере
//
// Возвращаемое значение:
//   Число - значение в секундах.
//
Функция ПериодЗаписи() Экспорт
	ТекущийПериод = Константы.ОценкаПроизводительностиПериодЗаписи.Получить();
	Возврат ?(ТекущийПериод > 60, ТекущийПериод, 60);
КонецФункции

// Процедура записи единичного замера
//
// Параметры:
//  КлючеваяОперация - СправочникСсылка.КлючевыеОперации - ключевая операция
//						либо Строка - название ключевой операции
//  Длительность - Число
//  ДатаНачалаКлючевойОперации - Дата.
//
Процедура ЗафиксироватьДлительностьКлючевойОперации(Параметры)

	КлючеваяОперация				 = Параметры.КлючеваяОперация;
	Длительность					 = Параметры.Длительность;
	ДатаНачалаКлючевойОперации		 = Параметры.ДатаНачалаКлючевойОперации;
	ДатаОкончанияКлючевойОперации	 = Параметры.ДатаОкончанияКлючевойОперации;
	ВесЗамера						 = Параметры.ВесЗамера;
	Комментарий						 = Параметры.Комментарий;
	Технологический					 = Параметры.Технологический;
	Длительная						 = Параметры.Длительная;
	ВыполненСОшибкой				 = Параметры.ВыполненСОшибкой;

	Если Не ЗначениеЗаполнено(ДатаНачалаКлючевойОперации) Тогда
		ЗаписьЖурналаРегистрации(НСтр("ru = 'Невозможно зафиксировать замер с пустой датой начала'",
			ОценкаПроизводительностиСлужебный.КодОсновногоЯзыка()), УровеньЖурналаРегистрации.Информация,,, Строка(
			КлючеваяОперация));

		Возврат;
	КонецЕсли;

	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);

	Если ТипЗнч(КлючеваяОперация) = Тип("Строка") Тогда
		КлючеваяОперацияСсылка = ОценкаПроизводительностиПовтИсп.ПолучитьКлючевуюОперациюПоИмени(КлючеваяОперация,
			Длительная);
	Иначе
		КлючеваяОперацияСсылка = КлючеваяОперация;
	КонецЕсли;

	Если Комментарий = Неопределено Тогда
		Комментарий = ПараметрыСеанса.КомментарийЗамераВремени;
	Иначе
		КомментарийПоУмолчанию = ОбщегоНазначения.JSONВЗначение(ПараметрыСеанса.КомментарийЗамераВремени);
		КомментарийПоУмолчанию.Вставить("ДопИнф", Комментарий);

		ЗаписьJSON = Новый ЗаписьJSON;
		ЗаписьJSON.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
		ЗаписатьJSON(ЗаписьJSON, КомментарийПоУмолчанию);
		Комментарий = ЗаписьJSON.Закрыть();
	КонецЕсли;

	Если Не Технологический Тогда
		Запись = РегистрыСведений.ЗамерыВремени.СоздатьМенеджерЗаписи();
	Иначе
		Запись = РегистрыСведений.ЗамерыВремениТехнологические.СоздатьМенеджерЗаписи();
	КонецЕсли;

	Запись.КлючеваяОперация = КлючеваяОперацияСсылка;
	
	// Получаем дату в UTC
	Запись.ДатаНачалаЗамера = ДатаНачалаКлючевойОперации;
	Запись.НомерСеанса = НомерСеансаИнформационнойБазы();

	Запись.ВремяВыполнения = ?(Длительность = 0, 0.001, Длительность); // Длительность меньше разрешения таймера
	Запись.ВесЗамера = ВесЗамера;

	Запись.ДатаЗаписи = Дата(1, 1, 1) + ТекущаяУниверсальнаяДатаВМиллисекундах() / 1000;
	Запись.ДатаЗаписиНачалоЧаса = НачалоЧаса(Запись.ДатаЗаписи);
	Если ДатаОкончанияКлючевойОперации <> Неопределено Тогда
		// Получаем дату в UTC
		Запись.ДатаОкончания = ДатаОкончанияКлючевойОперации;
	КонецЕсли;
	Запись.Пользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
	Запись.ДатаЗаписиЛокальная = ТекущаяДатаСеанса();
	Запись.Комментарий = Комментарий;
	Если Не Технологический Тогда
		Запись.ВыполненСОшибкой = ВыполненСОшибкой;
	КонецЕсли;

	Запись.Записать();

КонецПроцедуры

// Записывает переданный массив замеров.
// Элементы массива - тип структура.
// Запись осуществляется набором записей.
//   КлючеваяОперация - имя ключевой операции.
//   Длительность - длительность в миллисекундах.
//   ДатаНачалаКлючевойОперации - момент начала ключевой операции в миллисекундах.
//   ДатаОкончанияКлючевойОперации - момент окончания ключевой операции в миллисекундах.
//   Комментарий - произвольная строка, комментарий замера.
//   ВесЗамера - количество обработанных данных.
//   Длительная - признак, что длительность замера рассчитана на единицу веса.
//
Процедура ЗаписатьЗамерыВремени(МассивЗамеров)

	Если МонопольныйРежим() Тогда
		Возврат;
	КонецЕсли;

	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);

	НаборЗаписей = РегистрыСведений.ЗамерыВремени.СоздатьНаборЗаписей();
	НомерСеанса = НомерСеансаИнформационнойБазы();
	ДатаЗаписи = Дата(1, 1, 1) + ТекущаяУниверсальнаяДатаВМиллисекундах() / 1000;
	ДатаЗаписиНачалоЧаса = НачалоЧаса(ДатаЗаписи);
	Пользователь = ПользователиИнформационнойБазы.ТекущийПользователь();
	ДатаЗаписиЛокальная = ТекущаяДатаСеанса();

	Для Каждого Замер Из МассивЗамеров Цикл

		Если Не ЗначениеЗаполнено(Замер.ДатаНачалаКлючевойОперации) Тогда
			ЗаписьЖурналаРегистрации(НСтр("ru = 'Невозможно зафиксировать замер с пустой датой начала'",
				ОценкаПроизводительностиСлужебный.КодОсновногоЯзыка()), УровеньЖурналаРегистрации.Информация,,, Строка(
				Замер.КлючеваяОперация));
			Возврат;
		КонецЕсли;

		Если ТипЗнч(Замер.КлючеваяОперация) = Тип("Строка") Тогда
			КлючеваяОперацияСсылка = ОценкаПроизводительностиПовтИсп.ПолучитьКлючевуюОперациюПоИмени(
				Замер.КлючеваяОперация, Замер.Длительная);
		Иначе
			КлючеваяОперацияСсылка = Замер.КлючеваяОперация;
		КонецЕсли;

		Если Не ЗначениеЗаполнено(Замер.Комментарий) Тогда
			Комментарий = ПараметрыСеанса.КомментарийЗамераВремени;
		Иначе
			КомментарийПоУмолчанию = ОбщегоНазначения.JSONВЗначение(ПараметрыСеанса.КомментарийЗамераВремени);
			КомментарийПоУмолчанию.Вставить("ДопИнф", Замер.Комментарий);

			ЗаписьJSON = Новый ЗаписьJSON;
			ЗаписьJSON.УстановитьСтроку(Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Нет));
			ЗаписатьJSON(ЗаписьJSON, КомментарийПоУмолчанию);
			Комментарий = ЗаписьJSON.Закрыть();
		КонецЕсли;

		Запись = НаборЗаписей.Добавить();

		Запись.КлючеваяОперация = КлючеваяОперацияСсылка;
	
		// Получаем дату в UTC
		Запись.ДатаНачалаЗамера = Замер.ДатаНачалаКлючевойОперации;
		Запись.НомерСеанса = НомерСеанса;

		Запись.ВремяВыполнения = ?(Замер.Длительность = 0, 0.001, Замер.Длительность); // Длительность меньше разрешения таймера
		Запись.ВесЗамера = Замер.ВесЗамера;

		Запись.ДатаЗаписи = ДатаЗаписи;
		Запись.ДатаЗаписиНачалоЧаса = ДатаЗаписиНачалоЧаса;
		Если ЗначениеЗаполнено(Замер.ДатаОкончанияКлючевойОперации) Тогда
			// Получаем дату в UTC
			Запись.ДатаОкончания = Замер.ДатаОкончанияКлючевойОперации;
		КонецЕсли;
		Запись.Пользователь = Пользователь;
		Запись.ДатаЗаписиЛокальная = ДатаЗаписиЛокальная;
		Запись.Комментарий = Комментарий;

	КонецЦикла;

	Если НаборЗаписей.Количество() > 0 Тогда
		Попытка
			НаборЗаписей.Записать(Ложь);
		Исключение
			ЗаписьЖурналаРегистрации(НСтр("ru = 'Оценка производительности.Ошибка сохранения замеров'",
				ОценкаПроизводительностиСлужебный.КодОсновногоЯзыка()), УровеньЖурналаРегистрации.Ошибка,,,
				ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
		КонецПопытки;
	КонецЕсли;

	УстановитьПривилегированныйРежим(Ложь);
	УстановитьОтключениеБезопасногоРежима(Ложь);

КонецПроцедуры

// Процедура обработки регламентного задания по выгрузке данных
//
// Параметры:
//  КаталогиЭкспорта - Структура - структура со значением типа Массив.
//   ДопПараметры - Структура - дополнительные параметры.
//
Процедура ЭкспортОценкиПроизводительности(КаталогиЭкспорта, ДопПараметры = Неопределено) Экспорт

	Если ОценкаПроизводительностиСлужебный.ПодсистемаСуществует("СтандартныеПодсистемы.БазоваяФункциональность") Тогда
		МодульОбщегоНазначения = ОценкаПроизводительностиСлужебный.ОбщийМодуль("ОбщегоНазначения");
		МодульОбщегоНазначения.ПриНачалеВыполненияРегламентногоЗадания(
			Метаданные.РегламентныеЗадания.ЭкспортОценкиПроизводительности);
	КонецЕсли;
		
	// Если система отключена, то выгрузку данных делать не будем.
	Если ДопПараметры = Неопределено
		И Не ОценкаПроизводительностиВызовСервераПовтИсп.ВыполнятьЗамерыПроизводительности() Тогда
		Возврат;
	КонецЕсли;

	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	МАКСИМУМ(Замеры.ДатаЗаписи) КАК ДатаЗамера
	|ИЗ 
	|	РегистрСведений.ЗамерыВремени КАК Замеры
	|ГДЕ
	|	Замеры.ДатаЗаписи <= &ТекДата";

	Если ДопПараметры = Неопределено Тогда
		Запрос.УстановитьПараметр("ТекДата", ТекущаяУниверсальнаяДата() - 1);
	Иначе
		Запрос.УстановитьПараметр("ТекДата", ДопПараметры.ДатаОкончания);
	КонецЕсли;

	Выборка = Запрос.Выполнить().Выбрать();
	Если Выборка.Следующий() И Выборка.ДатаЗамера <> Null Тогда
		ВерхняяГраньДатыЗамеров = Выборка.ДатаЗамера;
	Иначе
		Возврат;
	КонецЕсли;

	МассивыЗамеров = ЗамерыСРазделениемПоКлючевымОперациям(ВерхняяГраньДатыЗамеров, ДопПараметры);

	ПорядковыйНомерФайла = 0;
	ТекДата = ТекущаяУниверсальнаяДатаВМиллисекундах();
	Если ДопПараметры <> Неопределено Тогда
		ВременныйКаталог = ПолучитьИмяВременногоФайла();
		СоздатьКаталог(ВременныйКаталог);

		КаталогиЭкспорта = Новый Структура("ЛокальныйКаталогЭкспорта, FTPКаталогЭкспорта", Новый Массив, Новый Массив);
		КаталогиЭкспорта.ЛокальныйКаталогЭкспорта.Добавить(Истина);
		КаталогиЭкспорта.ЛокальныйКаталогЭкспорта.Добавить(ВременныйКаталог);
		КаталогиЭкспорта.FTPКаталогЭкспорта.Добавить(Ложь);
		КаталогиЭкспорта.FTPКаталогЭкспорта.Добавить("");
	КонецЕсли;

	Для Каждого МассивЗамеров Из МассивыЗамеров Цикл
		ПорядковыйНомерФайла = ПорядковыйНомерФайла + 1;
		ВыгрузитьРезультаты(КаталогиЭкспорта, МассивЗамеров, ТекДата, ПорядковыйНомерФайла);
	КонецЦикла;

	Если ДопПараметры <> Неопределено Тогда
		ФайлыДанных = НайтиФайлы(ВременныйКаталог, "*.xml");
		ЗаписьZipФайла = Новый ЗаписьZipФайла;
		ИмяАрхива = ПолучитьИмяВременногоФайла("zip");
		ЗаписьZipФайла.Открыть(ИмяАрхива,,, МетодСжатияZIP.Сжатие);
		Для Каждого ФайлДанных Из ФайлыДанных Цикл
			ЗаписьZipФайла.Добавить(ФайлДанных.ПолноеИмя);
		КонецЦикла;
		ЗаписьZipФайла.Записать();

		ДвоичныеДанныеАрхив = Новый ДвоичныеДанные(ИмяАрхива);
		ПоместитьВоВременноеХранилище(ДвоичныеДанныеАрхив, ДопПараметры.АдресХранилища);

		УдалитьФайлы(ВременныйКаталог);
		УдалитьФайлы(ИмяАрхива);
	КонецЕсли;

КонецПроцедуры

// Процедура обработки регламентного задания по очистке регистров замеров
Процедура ОчиститьРегистрыЗамерыВремени() Экспорт

	Если ОценкаПроизводительностиСлужебный.ПодсистемаСуществует("СтандартныеПодсистемы.БазоваяФункциональность") Тогда
		МодульОбщегоНазначения = ОценкаПроизводительностиСлужебный.ОбщийМодуль("ОбщегоНазначения");
		МодульОбщегоНазначения.ПриНачалеВыполненияРегламентногоЗадания(
			Метаданные.РегламентныеЗадания.ОчисткаЗамеровВремени);
	КонецЕсли;

	ГраницаУдаления = НачалоДня(ТекущаяУниверсальнаяДата() - 86400 * Константы.ПериодХраненияЗамеров.Получить());

	ЗапросЗамерыВремени = Новый Запрос;
	ЗапросЗамерыВремени.Текст = "
								|ВЫБРАТЬ
								|	МИНИМУМ(ДатаЗаписиНачалоЧаса) КАК ДатаЗаписиНачалоЧаса
								|ИЗ
								|	РегистрСведений.ЗамерыВремени
								|ГДЕ
								|	ДатаЗаписиНачалоЧаса < &ГраницаУдаления
								|";
	ЗапросЗамерыВремени.УстановитьПараметр("ГраницаУдаления", ГраницаУдаления);

	ЗапросЗамерыВремениТехнологические = Новый Запрос;
	ЗапросЗамерыВремениТехнологические.Текст = "
											   |ВЫБРАТЬ
											   |	МИНИМУМ(ДатаЗаписиНачалоЧаса) КАК ДатаЗаписиНачалоЧаса
											   |ИЗ
											   |	РегистрСведений.ЗамерыВремениТехнологические
											   |ГДЕ
											   |	ДатаЗаписиНачалоЧаса < &ГраницаУдаления
											   |";
	ЗапросЗамерыВремениТехнологические.УстановитьПараметр("ГраницаУдаления", ГраницаУдаления);

	НаборЗаписей = РегистрыСведений.ЗамерыВремени.СоздатьНаборЗаписей();
	НаборЗаписейТехнологические = РегистрыСведений.ЗамерыВремениТехнологические.СоздатьНаборЗаписей();

	ЕстьУдаление = Истина;
	ЕстьУдалениеТехнологические = Истина;
	Пока ЕстьУдаление Или ЕстьУдалениеТехнологические Цикл

		Если ЕстьУдаление Тогда
			ЕстьУдаление = Ложь;

			Результат = ЗапросЗамерыВремени.Выполнить(); // @skip-check query-in-loop - порционная обработка данных.
			Выборка = Результат.Выбрать();
			Выборка.Следующий();
			ДатаЗаписиНачалоЧаса = Выборка.ДатаЗаписиНачалоЧаса;

			Если Не ДатаЗаписиНачалоЧаса = Null Тогда
				НаборЗаписей.Отбор.ДатаЗаписиНачалоЧаса.Установить(ДатаЗаписиНачалоЧаса);
				НаборЗаписей.Записать(Истина);
				ЕстьУдаление = Истина;
			КонецЕсли;
		КонецЕсли;

		Если ЕстьУдалениеТехнологические Тогда
			ЕстьУдалениеТехнологические = Ложь;
			Результат = ЗапросЗамерыВремениТехнологические.Выполнить(); // @skip-check query-in-loop - порционная обработка данных.
			Выборка = Результат.Выбрать();
			Выборка.Следующий();
			ДатаЗаписиНачалоЧаса = Выборка.ДатаЗаписиНачалоЧаса;
			Если Не ДатаЗаписиНачалоЧаса = Null Тогда
				НаборЗаписейТехнологические.Отбор.ДатаЗаписиНачалоЧаса.Установить(ДатаЗаписиНачалоЧаса);
				НаборЗаписейТехнологические.Записать(Истина);
				ЕстьУдалениеТехнологические = Истина;
			КонецЕсли;
		КонецЕсли;

	КонецЦикла;

КонецПроцедуры

// Регламентное задание экспорта
// Параметры:
//   ДопПараметры - Структура:
//   * ДатаНачала - Дата - дата начала замеров
//   * ДатаОкончания - Дата - дата окончания замеров.
//
Функция ЗамерыСРазделениемПоКлючевымОперациям(ВерхняяГраньДатыЗамеров, ДопПараметры = Неопределено)

	Запрос = Новый Запрос;

	УровниПроизводительностиЧисло = Новый Соответствие;
	УровниПроизводительностиЧисло.Вставить(Перечисления.УровниПроизводительности.Отлично, 1);
	УровниПроизводительностиЧисло.Вставить(Перечисления.УровниПроизводительности.Хорошо, 0.94);
	УровниПроизводительностиЧисло.Вставить(Перечисления.УровниПроизводительности.Плохо, 0.85);
	УровниПроизводительностиЧисло.Вставить(Перечисления.УровниПроизводительности.ОченьПлохо, 0.70);
	УровниПроизводительностиЧисло.Вставить(Перечисления.УровниПроизводительности.Неприемлемо, 0.50);

	Если ДопПараметры = Неопределено Тогда
		ТекстЗапроса = ЗамерыВремениБезОтбораПоПрофилю();
		Запрос.УстановитьПараметр("ДатаПоследнейВыгрузки",
			Константы.ДатаПоследнейВыгрузкиЗамеровПроизводительностиUTC.Получить());
		Запрос.УстановитьПараметр("ВерхняяГраньДатыЗамеров", ВерхняяГраньДатыЗамеров);
		Константы.ДатаПоследнейВыгрузкиЗамеровПроизводительностиUTC.Установить(ВерхняяГраньДатыЗамеров);
	Иначе
		Если ЗначениеЗаполнено(ДопПараметры.Профиль) Тогда
			ТекстЗапроса = ЗамерыВремениСОтборомПоПрофилю();
			Запрос.УстановитьПараметр("Профиль", ДопПараметры.Профиль);
		Иначе
			ТекстЗапроса = ЗамерыВремениБезОтбораПоПрофилю();
		КонецЕсли;
		Запрос.УстановитьПараметр("ДатаПоследнейВыгрузки", ДопПараметры.ДатаНачала);
		Запрос.УстановитьПараметр("ВерхняяГраньДатыЗамеров", ДопПараметры.ДатаОкончания);
	КонецЕсли;

	Запрос.Текст = ТекстЗапроса;

	Результат = Запрос.Выполнить();

	КоличествоЗамеровВФайле = Константы.КоличествоЗамеровВПакетеЭкспорта.Получить();
	КоличествоЗамеровВФайле = ?(КоличествоЗамеровВФайле <> 0, КоличествоЗамеровВФайле, 1000);

	ЗамерыПоФайлам = Новый Массив;

	ЗамерыСРазделением = Новый Соответствие;
	ТекКоличествоЗамеров = 0;

	Выборка = Результат.Выбрать();
	Пока Выборка.Следующий() Цикл
		КлючеваяОперация = ЗамерыСРазделением.Получить(Выборка.КлючеваяОперация);

		Если КлючеваяОперация = Неопределено Тогда
			КлючеваяОперация = Новый Соответствие;
			КлючеваяОперация.Вставить("uid", Строка(Выборка.КлючеваяОперация.УникальныйИдентификатор()));
			КлючеваяОперация.Вставить("name", Выборка.КлючеваяОперацияСтрока);
			КлючеваяОперация.Вставить("nameFull", Выборка.КлючеваяОперацияИмя);
			КлючеваяОперация.Вставить("comments", Новый Соответствие);
			КлючеваяОперация.Вставить("priority", Выборка.КлючеваяОперацияПриоритет);
			КлючеваяОперация.Вставить("targetValue", Выборка.КлючеваяОперацияЦелевоеВремя);
			КлючеваяОперация.Вставить("minimalApdexValue",
				УровниПроизводительностиЧисло[Выборка.КлючеваяОперация.МинимальноДопустимыйУровень]);
			КлючеваяОперация.Вставить("long", Выборка.Длительная);

			ЗамерыСРазделением.Вставить(Выборка.КлючеваяОперация, КлючеваяОперация);
		КонецЕсли;

		Комментарий = ЗамерыСРазделением[Выборка.КлючеваяОперация]["comments"][Выборка.Комментарий];
		Если Комментарий = Неопределено Тогда
			Комментарий = Новый Соответствие;
			Комментарий.Вставить("Замеры", Новый Массив);
			КлючеваяОперация["comments"].Вставить(Выборка.Комментарий, Комментарий);
		КонецЕсли;

		КлючеваяОперацияЗамеры = Комментарий.Получить("Замеры"); // Массив

		ЗамерСтруктура = Новый Структура;
		ЗамерСтруктура.Вставить("value", Выборка.ВремяВыполнения);
		ЗамерСтруктура.Вставить("weight", Выборка.ВесЗамера);
		ЗамерСтруктура.Вставить("tUTC", Выборка.ДатаНачалаЗамера);
		ЗамерСтруктура.Вставить("userName", Выборка.Пользователь);
		ЗамерСтруктура.Вставить("tSaveUTC", Выборка.ДатаЗаписи);
		ЗамерСтруктура.Вставить("sessionNumber", Выборка.НомерСеанса);
		ЗамерСтруктура.Вставить("comment", Выборка.Комментарий);
		ЗамерСтруктура.Вставить("runningError", Выборка.ВыполненСОшибкой);

		КлючеваяОперацияЗамеры.Добавить(ЗамерСтруктура);

		ТекКоличествоЗамеров = ТекКоличествоЗамеров + 1;

		Если ТекКоличествоЗамеров = КоличествоЗамеровВФайле Тогда
			ЗамерыПоФайлам.Добавить(ЗамерыСРазделением);
			ЗамерыСРазделением = Новый Соответствие;
			КлючеваяОперация = Неопределено;
			ТекКоличествоЗамеров = 0;
		КонецЕсли;
	КонецЦикла;
	ЗамерыПоФайлам.Добавить(ЗамерыСРазделением);

	Возврат ЗамерыПоФайлам;
КонецФункции

// Сохраняет результаты вычисления APDEX в файл
//
// Параметры:
//  КаталогиЭкспорта - структура со значением типа Массив.
//  ВыборкаАпдекс - результат запроса
//  МассивыЗамеров - структура со значением типа Массив.
//
Процедура ВыгрузитьРезультаты(КаталогиЭкспорта, МассивыЗамеров, ТекДата, ПорядковыйНомерФайла)

	ПространствоИмен = "www.v8.1c.ru/ssl/performace-assessment/apdexExport/1.0.0.4";
	ИмяВременногоФайла = ПолучитьИмяВременногоФайла(".xml");

	ЗаписьXML = Новый ЗаписьXML;
	ЗаписьXML.ОткрытьФайл(ИмяВременногоФайла, "UTF-8");
	ЗаписьXML.ЗаписатьОбъявлениеXML();
	ЗаписьXML.ЗаписатьНачалоЭлемента("Performance", ПространствоИмен);
	ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("prf", ПространствоИмен);
	ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("xs", "http://www.w3.org/2001/XMLSchema");
	ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("xsi", "http://www.w3.org/2001/XMLSchema-instance");

	ЗаписьXML.ЗаписатьАтрибут("version", ПространствоИмен, "1.0.0.4");
	ЗаписьXML.ЗаписатьАтрибут("period", ПространствоИмен, Строка(Дата(1, 1, 1) + ТекДата / 1000));

	ТипКлючеваяОперация = ФабрикаXDTO.Тип(ПространствоИмен, "KeyOperation");
	ТипИзмерение = ФабрикаXDTO.Тип(ПространствоИмен, "Measurement");

	Для Каждого ТекЗамер Из МассивыЗамеров Цикл
		КлючеваяОперацияЗамеры = ТекЗамер.Значение;

		Для Каждого Комментарий Из КлючеваяОперацияЗамеры["comments"] Цикл
			КлючеваяОперация = ФабрикаXDTO.Создать(ТипКлючеваяОперация); // см. XDTOПакет.ApdexExport.KeyOperation
			КлючеваяОперация.name = КлючеваяОперацияЗамеры["name"];
			КлючеваяОперация.nameFull = КлючеваяОперацияЗамеры["nameFull"];
			КлючеваяОперация.long = КлючеваяОперацияЗамеры["long"];
			КлючеваяОперация.comment = Комментарий.Ключ;
			КлючеваяОперация.priority = КлючеваяОперацияЗамеры["priority"];
			КлючеваяОперация.targetValue = КлючеваяОперацияЗамеры["targetValue"];
			КлючеваяОперация.uid = КлючеваяОперацияЗамеры["uid"];

			Замеры = Комментарий.Значение["Замеры"];
			Для Каждого Замер Из Замеры Цикл
				ЗамерXML = ФабрикаXDTO.Создать(ТипИзмерение);
				ЗамерXML.value = Замер.value;
				ЗамерXML.weight = Замер.weight;
				ЗамерXML.tUTC = Замер.tUTC;
				ЗамерXML.userName = Замер.userName;
				ЗамерXML.tSaveUTC = Замер.tSaveUTC;
				ЗамерXML.sessionNumber = Замер.sessionNumber;
				ЗамерXML.runningError = Замер.runningError;

				КлючеваяОперация.measurement.Добавить(ЗамерXML);
			КонецЦикла;

			ФабрикаXDTO.ЗаписатьXML(ЗаписьXML, КлючеваяОперация);
		КонецЦикла;
	КонецЦикла;
	ЗаписьXML.ЗаписатьКонецЭлемента();
	ЗаписьXML.Закрыть();

	Для Каждого КлючВыполнятьКаталог Из КаталогиЭкспорта Цикл
		ВыполнятьКаталог = КлючВыполнятьКаталог.Значение;
		Выполнять = ВыполнятьКаталог[0];
		Если Не Выполнять Тогда
			Продолжить;
		КонецЕсли;

		КаталогЭкспорта = ВыполнятьКаталог[1];
		Ключ = КлючВыполнятьКаталог.Ключ;
		Если Ключ = ОценкаПроизводительностиКлиентСервер.ЛокальныйКаталогЭкспортаКлючЗадания() Тогда
			СоздатьКаталог(КаталогЭкспорта);
		КонецЕсли;

		КопироватьФайл(ИмяВременногоФайла, ПолноеИмяФайлаЭкспорта(КаталогЭкспорта, ТекДата, ПорядковыйНомерФайла,
			".xml"));
	КонецЦикла;
	УдалитьФайлы(ИмяВременногоФайла);
КонецПроцедуры

Функция ЗамерыВремениБезОтбораПоПрофилю()
	Возврат "
			|ВЫБРАТЬ
			|	Замеры.КлючеваяОперация КАК КлючеваяОперация,
			|	Замеры.ДатаНачалаЗамера КАК ДатаНачалаЗамера,
			|	Замеры.ВремяВыполнения КАК ВремяВыполнения,
			|	Замеры.ВесЗамера КАК ВесЗамера,
			|	Замеры.Пользователь КАК Пользователь,
			|	Замеры.ДатаЗаписи КАК ДатаЗаписи,
			|	Замеры.НомерСеанса КАК НомерСеанса,
			|	Замеры.Комментарий КАК Комментарий, 
			|	КлючевыеОперации.Наименование КАК КлючеваяОперацияСтрока,
			|	КлючевыеОперации.Имя КАК КлючеваяОперацияИмя,
			|	КлючевыеОперации.Приоритет КАК КлючеваяОперацияПриоритет,
			|	КлючевыеОперации.ЦелевоеВремя КАК КлючеваяОперацияЦелевоеВремя,
			|	КлючевыеОперации.МинимальноДопустимыйУровень КАК МинимальноДопустимыйУровень,
			|	Замеры.ВыполненСОшибкой КАК ВыполненСОшибкой,
			|	КлючевыеОперации.Длительная КАК Длительная
			|ИЗ
			|	РегистрСведений.ЗамерыВремени КАК Замеры
			|ВНУТРЕННЕЕ СОЕДИНЕНИЕ
			|	Справочник.КлючевыеОперации КАК КлючевыеОперации
			|ПО
			|	КлючевыеОперации.Ссылка = Замеры.КлючеваяОперация
			|ГДЕ
			|	Замеры.ДатаЗаписи > &ДатаПоследнейВыгрузки
			|	И Замеры.ДатаЗаписи <= &ВерхняяГраньДатыЗамеров
			|УПОРЯДОЧИТЬ ПО
			|	Замеры.КлючеваяОперация,
			|	Замеры.Комментарий
			|";
КонецФункции

Функция ЗамерыВремениСОтборомПоПрофилю()
	Возврат "
			|ВЫБРАТЬ
			|	Замеры.КлючеваяОперация КАК КлючеваяОперация,
			|	Замеры.ДатаНачалаЗамера КАК ДатаНачалаЗамера,
			|	Замеры.ВремяВыполнения КАК ВремяВыполнения,
			|	Замеры.ВесЗамера КАК ВесЗамера,
			|	Замеры.Пользователь КАК Пользователь,
			|	Замеры.ДатаЗаписи КАК ДатаЗаписи,
			|	Замеры.НомерСеанса КАК НомерСеанса,
			|	Замеры.Комментарий КАК Комментарий, 
			|	КлючевыеОперации.КлючеваяОперация.Наименование КАК КлючеваяОперацияСтрока,
			|	КлючевыеОперации.КлючеваяОперация.Имя КАК КлючеваяОперацияИмя,
			|	КлючевыеОперации.Приоритет КАК КлючеваяОперацияПриоритет,
			|	КлючевыеОперации.ЦелевоеВремя КАК КлючеваяОперацияЦелевоеВремя,
			|	КлючевыеОперации.КлючеваяОперация.МинимальноДопустимыйУровень КАК МинимальноДопустимыйУровень,
			|	Замеры.ВыполненСОшибкой КАК ВыполненСОшибкой,
			|	КлючевыеОперации.КлючеваяОперация.Длительная КАК Длительная
			|ИЗ
			|	РегистрСведений.ЗамерыВремени КАК Замеры
			|ВНУТРЕННЕЕ СОЕДИНЕНИЕ
			|	Справочник.ПрофилиКлючевыхОпераций.КлючевыеОперацииПрофиля КАК КлючевыеОперации
			|ПО
			|	КлючевыеОперации.КлючеваяОперация = Замеры.КлючеваяОперация
			|	И КлючевыеОперации.Ссылка = &Профиль
			|ГДЕ
			|	Замеры.ДатаЗаписи > &ДатаПоследнейВыгрузки
			|	И Замеры.ДатаЗаписи <= &ВерхняяГраньДатыЗамеров
			|УПОРЯДОЧИТЬ ПО
			|	Замеры.КлючеваяОперация,
			|	Замеры.Комментарий
			|";
КонецФункции

// Генерирует имя файла для экспорта
//
// Параметры:
//  Каталог - Строка, 
//  ДатаФормированияФайла - Дата - дата и время выполнения замера
//  РасширениеСТочкой - Строка - задающая расширение файла, в виде ".xxx". 
// Возвращаемое значение:
//  Строка - полный путь к файлу экспорта.
//
Функция ПолноеИмяФайлаЭкспорта(Каталог, ТекДата, ПорядковыйНомерФайла, РасширениеСТочкой)

	ДатаФормированияФайла = Дата(1, 1, 1) + ТекДата / 1000;
	ПорядковыйНомерФайлаФормат = Формат(ПорядковыйНомерФайла, "ЧЦ=5; ЧВН=; ЧГ=0");

	Разделитель = ?(ВРег(Лев(Каталог, 3)) = "FTP", "/", ПолучитьРазделительПути());
	// АПК:1367-выкл Фиксированный нелокализуемый шаблон имени файла.
	Возврат УбратьРазделителиНаКонцеИмениФайла(Каталог, Разделитель) + Разделитель + Формат(ДатаФормированияФайла,
		"ДФ='гггг-ММ-дд ЧЧ-мм-сс-" + ПорядковыйНомерФайлаФормат + "'") + РасширениеСТочкой;
	// АПК:1367-вкл

КонецФункции

// Проверить путь на наличие завершающего слеша и если он есть, удалить его
//
// Параметры:
//  ИмяФайла - Строка
//  Разделитель - Строка
//
Функция УбратьРазделителиНаКонцеИмениФайла(Знач ИмяФайла, Разделитель)

	ДлинаПути = СтрДлина(ИмяФайла);
	Если ДлинаПути = 0 Тогда
		Возврат ИмяФайла;
	КонецЕсли;

	Пока ДлинаПути > 0 И СтрЗаканчиваетсяНа(ИмяФайла, Разделитель) Цикл
		ИмяФайла = Лев(ИмяФайла, ДлинаПути - 1);
		ДлинаПути = СтрДлина(ИмяФайла);
	КонецЦикла;

	Возврат ИмяФайла;

КонецФункции

Процедура ЗагрузитьФайлОценкиПроизводительности(ИмяФайла, АдресХранилища) Экспорт

	ФайлХранилища = ПолучитьИмяВременногоФайла("zip");
	ДвоичныеДанныеАрхива = ПолучитьИзВременногоХранилища(АдресХранилища); // ДвоичныеДанные
	ДвоичныеДанныеАрхива.Записать(ФайлХранилища);

	КаталогФайлов = ДобавитьКонечныйРазделительПути(ПолучитьИмяВременногоФайла());

	Попытка
		ЧтениеZIP = Новый ЧтениеZipФайла(ФайлХранилища);
		ЧтениеZIP.ИзвлечьВсе(КаталогФайлов, РежимВосстановленияПутейФайловZIP.НеВосстанавливать);
		ЧтениеZIP.Закрыть();
	Исключение
		УдалитьФайлы(ФайлХранилища);
		УдалитьФайлы(КаталогФайлов);
		ОписаниеОшибки = ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
		ШаблонСтроки = НСтр("ru = 'Не удалось распаковать архив %1 %2'");
		ОписаниеИсключения = ОценкаПроизводительностиКлиентСервер.ПодставитьПараметрыВСтроку(ШаблонСтроки, ИмяФайла,
			ОписаниеОшибки);
		ВызватьИсключение ОписаниеИсключения;
	КонецПопытки;

	Попытка
		СуществующиеКлючевыеОперации = СуществующиеКлючевыеОперации();
		КлючевыеОперацииДляЗаписи = Новый Массив;
		СырыеЗамерыДляЗаписи = Новый Массив;

		Для Каждого Файл Из НайтиФайлы(КаталогФайлов, "*.XML") Цикл
			ЧтениеXML = Новый ЧтениеXML;
			ЧтениеXML.ОткрытьФайл(Файл.ПолноеИмя);
			ЧтениеXML.ПерейтиКСодержимому();

			// @skip-check query-in-loop - порционная обработка данных.
			ЗагрузитьФайлОценкиПроизводительностиApdexExport(ЧтениеXML, СуществующиеКлючевыеОперации,
				КлючевыеОперацииДляЗаписи, СырыеЗамерыДляЗаписи);  
			ЧтениеXML.Закрыть();
		КонецЦикла;
		УдалитьФайлы(ФайлХранилища);
		УдалитьФайлы(КаталогФайлов);
	Исключение
		ЧтениеXML.Закрыть();
		УдалитьФайлы(ФайлХранилища);
		УдалитьФайлы(КаталогФайлов);
		ВызватьИсключение;
	КонецПопытки;

	НачатьТранзакцию();
	Попытка
		Для Каждого СыройЗамерДляЗаписи Из СырыеЗамерыДляЗаписи Цикл
			Запись = РегистрыСведений.ЗамерыВремени.СоздатьМенеджерЗаписи();
			Для Каждого КлючИЗначение Из СыройЗамерДляЗаписи Цикл
				Запись[КлючИЗначение.Ключ] = КлючИЗначение.Значение;
			КонецЦикла;
			Запись.ДатаЗаписиНачалоЧаса = НачалоЧаса(Запись.ДатаЗаписи);
			Запись.ДатаОкончания = Запись.ДатаНачалаЗамера + Запись.ВремяВыполнения * 1000;
			Запись.Записать();
		КонецЦикла;
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;

КонецПроцедуры

Процедура ЗагрузитьФайлОценкиПроизводительностиApdexExport(ЧтениеXML, СуществующиеКлючевыеОперации,
	КлючевыеОперацииДляЗаписи, СырыеЗамерыДляЗаписи)

	ПространствоИмен = ЧтениеXML.URIПространстваИмен;
	
	// С версии 1.0.0.4 факт выполнения замера с ошибкой хранится в самом замере, а не в ключевой операции.
	ПризнакОшибкиВЗамере = Метаданные.ПакетыXDTO.ApdexExport_1_0_0_4.ПространствоИмен = ПространствоИмен;

	Замеры = "measurement";
	ТипКлючеваяОперация = ФабрикаXDTO.Тип(ПространствоИмен, "KeyOperation");

	ЧтениеXML.Прочитать();

	Пока ЧтениеXML.ТипУзла <> ТипУзлаXML.КонецЭлемента Цикл

		КлючеваяОперация = ФабрикаXDTO.ПрочитатьXML(ЧтениеXML, ТипКлючеваяОперация);

		КлючеваяОперацияИмя = КлючеваяОперация.nameFull;
		ЦелевоеВремя = КлючеваяОперация.targetValue;
		Комментарий = КлючеваяОперация.comment;
		Длительная = КлючеваяОперация.long;

		КлючеваяОперацияСсылка = СуществующиеКлючевыеОперации[КлючеваяОперацияИмя];
		Если КлючеваяОперацияСсылка = Неопределено Тогда
			КлючеваяОперацияСсылка = СоздатьКлючевуюОперацию(КлючеваяОперацияИмя, ЦелевоеВремя, Длительная);  // @skip-check query-in-loop - создание операции в случае отсутствия.
			СуществующиеКлючевыеОперации.Вставить(КлючеваяОперацияИмя, КлючеваяОперацияСсылка);
		КонецЕсли;

		МаксимальнаяДатаЗамера = Неопределено;
		ЧислоЗамеров = КлючеваяОперация[Замеры].Количество();
		НомерЗамера = 0;
		Пока НомерЗамера < ЧислоЗамеров Цикл
			Замер = КлючеваяОперация[Замеры].Получить(НомерЗамера);
			ДатаЗамера = Замер.tUTC;
			Если МаксимальнаяДатаЗамера = Неопределено Или МаксимальнаяДатаЗамера < ДатаЗамера Тогда
				МаксимальнаяДатаЗамера = ДатаЗамера;
			КонецЕсли;
			Если ПризнакОшибкиВЗамере Тогда
				ВыполненСОшибкой = Замер.runningError;
			Иначе
				ВыполненСОшибкой = КлючеваяОперация.runningError;
			КонецЕсли;

			СыройЗамерДляЗаписи = Новый Соответствие;
			СыройЗамерДляЗаписи.Вставить("КлючеваяОперация", КлючеваяОперацияСсылка);
			СыройЗамерДляЗаписи.Вставить("ДатаНачалаЗамера", Замер.tUTC);
			СыройЗамерДляЗаписи.Вставить("ВремяВыполнения", Замер.value);
			СыройЗамерДляЗаписи.Вставить("ВесЗамера", Замер.weight);
			СыройЗамерДляЗаписи.Вставить("Пользователь", Замер.userName);
			СыройЗамерДляЗаписи.Вставить("ДатаЗаписи", Замер.tSaveUTC);
			СыройЗамерДляЗаписи.Вставить("НомерСеанса", Замер.sessionNumber);
			СыройЗамерДляЗаписи.Вставить("Комментарий", Комментарий);
			СыройЗамерДляЗаписи.Вставить("ВыполненСОшибкой", ВыполненСОшибкой);

			СырыеЗамерыДляЗаписи.Добавить(СыройЗамерДляЗаписи);

			НомерЗамера = НомерЗамера + 1;
		КонецЦикла;
	КонецЦикла;

КонецПроцедуры

Функция СуществующиеКлючевыеОперации()
	СуществующиеКлючевыеОперации = Новый Соответствие;
	Запрос = Новый Запрос("ВЫБРАТЬ
						  |	КлючевыеОперации.Ссылка КАК Ссылка,
						  |	КлючевыеОперации.Имя КАК Имя
						  |ИЗ
						  |	Справочник.КлючевыеОперации КАК КлючевыеОперации");
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		СуществующиеКлючевыеОперации.Вставить(Выборка.Имя, Выборка.Ссылка);
	КонецЦикла;
	Возврат СуществующиеКлючевыеОперации;
КонецФункции

// Добавляет к переданному пути каталога конечный символ-разделитель, если он отсутствует.
//
// Параметры:
//  ПутьКаталога - Строка - путь к каталогу.
//  Платформа - ТипПлатформы - параметр устарел, больше не используется.
//
// Возвращаемое значение:
//  Строка - путь к каталогу с конечным символом-разделителем.
//
// Пример:
//  Результат = ДобавитьКонечныйРазделительПути("C:\Мой каталог"); // возвращает "C:\Мой каталог\".
//  Результат = ДобавитьКонечныйРазделительПути("C:\Мой каталог\"); // возвращает "C:\Мой каталог\".
//  Результат = ДобавитьКонечныйРазделительПути("%APPDATA%"); // возвращает "%APPDATA%\".
//
Функция ДобавитьКонечныйРазделительПути(Знач ПутьКаталога, Знач Платформа = Неопределено)
	Если ПустаяСтрока(ПутьКаталога) Тогда
		Возврат ПутьКаталога;
	КонецЕсли;

	ДобавляемыйСимвол = ПолучитьРазделительПути();

	Если СтрЗаканчиваетсяНа(ПутьКаталога, ДобавляемыйСимвол) Тогда
		Возврат ПутьКаталога;
	Иначе
		Возврат ПутьКаталога + ДобавляемыйСимвол;
	КонецЕсли;
КонецФункции

#КонецОбласти